package com.xxl.job.admin.core.schedule;

import java.util.Date;
import java.util.HashSet;
import java.util.concurrent.ConcurrentHashMap;

import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.Job;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.Trigger.TriggerState;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.quartz.impl.triggers.CronTriggerImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import com.xxl.job.admin.core.jobbean.RemoteHttpJobBean;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.thread.JobFailMonitorHelper;
import com.xxl.job.admin.core.thread.JobRegistryMonitorHelper;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.admin.dao.XxlJobGroupDao;
import com.xxl.job.admin.dao.XxlJobInfoDao;
import com.xxl.job.admin.dao.XxlJobLogDao;
import com.xxl.job.admin.dao.XxlJobRegistryDao;
import com.xxl.job.core.biz.AdminBiz;
import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
import com.xxl.job.core.rpc.netcom.NetComClientProxy;
import com.xxl.job.core.rpc.netcom.NetComServerFactory;

/**
 * base quartz scheduler util
 * 
 * @author xuxueli 2015-12-19 16:13:53
 */
@Component // 此注解必加
@EnableScheduling
// final 去掉了 jia'le加了@Configuration
public class XxlJobDynamicScheduler implements ApplicationContextAware {
	private static final Logger logger = LoggerFactory.getLogger(XxlJobDynamicScheduler.class);

	// ---------------------- param ----------------------

	// scheduler
	// @Resource(name = "scheduler")
	@Autowired
	private XxlJobDynamicScheduler XxlJobDynamicScheduler;

	// Scheduler scheduler =new QuartzConfig().Scheduler().
	@Autowired
	private Scheduler scheduler;
	// public void setScheduler(Scheduler scheduler) {
	// scheduler = new QuartzConfig().schedulerFactoryBean().getScheduler();
	// }

	// accessToken
	@Value("${xxl.job.accessToken}")
	private String accessToken;
	// dao
	@Autowired
	public XxlJobLogDao xxlJobLogDao;
	@Autowired
	public XxlJobInfoDao xxlJobInfoDao;
	@Autowired
	public XxlJobRegistryDao xxlJobRegistryDao;
	@Autowired
	public XxlJobGroupDao xxlJobGroupDao;
	@Autowired
	public AdminBiz adminBiz;

	// ---------------------- applicationContext ----------------------
	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		xxlJobLogDao = applicationContext.getBean(XxlJobLogDao.class);
		xxlJobInfoDao = applicationContext.getBean(XxlJobInfoDao.class);
		xxlJobRegistryDao = applicationContext.getBean(XxlJobRegistryDao.class);
		xxlJobGroupDao = applicationContext.getBean(XxlJobGroupDao.class);
		adminBiz = applicationContext.getBean(AdminBiz.class);
	}

	// ---------------------- init + destroy ----------------------
	public void init() throws Exception {
		// admin registry monitor run
		JobRegistryMonitorHelper.getInstance().start();

		// admin monitor run
		JobFailMonitorHelper.getInstance().start();

		// admin-server(spring-mvc)
		NetComServerFactory.putService(AdminBiz.class, adminBiz);
		NetComServerFactory.setAccessToken(accessToken);

		// init i18n
		initI18n();

		// valid
		Assert.notNull(scheduler, "quartz scheduler is null");
		logger.info(">>>>>>>>> init xxl-job admin success.");
	}

	private void initI18n() {
		for (ExecutorBlockStrategyEnum item : ExecutorBlockStrategyEnum.values()) {
			item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
		}
	}

	public void destroy() {
		// admin registry stop
		JobRegistryMonitorHelper.getInstance().toStop();

		// admin monitor stop
		JobFailMonitorHelper.getInstance().toStop();
	}

	// ---------------------- executor-client ----------------------
	private ConcurrentHashMap<String, ExecutorBiz> executorBizRepository = new ConcurrentHashMap<String, ExecutorBiz>();

	public ExecutorBiz getExecutorBiz(String address) throws Exception {
		// valid
		if (address == null || address.trim().length() == 0) {
			return null;
		}

		// load-cache
		address = address.trim();
		ExecutorBiz executorBiz = executorBizRepository.get(address);
		if (executorBiz != null) {
			return executorBiz;
		}

		// set-cache
		executorBiz = (ExecutorBiz) new NetComClientProxy(ExecutorBiz.class, address, accessToken).getObject();
		executorBizRepository.put(address, executorBiz);
		return executorBiz;
	}

	// ---------------------- schedule util ----------------------

	/**
	 * fill job info
	 *
	 * @param jobInfo
	 */
	public void fillJobInfo(XxlJobInfo jobInfo) {
		// TriggerKey : name + group
		String group = String.valueOf(jobInfo.getJobGroup());
		String name = String.valueOf(jobInfo.getId());
		TriggerKey triggerKey = TriggerKey.triggerKey(name, group);

		try {
			Trigger trigger = scheduler.getTrigger(triggerKey);

			TriggerState triggerState = scheduler.getTriggerState(triggerKey);

			// parse params
			if (trigger != null && trigger instanceof CronTriggerImpl) {
				String cronExpression = ((CronTriggerImpl) trigger).getCronExpression();
				jobInfo.setJobCron(cronExpression);
			}

			// JobKey jobKey = new JobKey(jobInfo.getJobName(),
			// String.valueOf(jobInfo.getJobGroup()));
			// JobDetail jobDetail = scheduler.getJobDetail(jobKey);
			// String jobClass = jobDetail.getJobClass().getName();

			if (triggerState != null) {
				jobInfo.setJobStatus(triggerState.name());
			}

		} catch (SchedulerException e) {
			logger.error(e.getMessage(), e);
		}
	}

	/**
	 * check if exists
	 *
	 * @param jobName
	 * @param jobGroup
	 * @return
	 * @throws SchedulerException
	 */
	public boolean checkExists(String jobName, String jobGroup) throws SchedulerException {
		TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
		return scheduler.checkExists(triggerKey);
	}

	/**
	 * addJob
	 *
	 * @param jobName
	 * @param jobGroup
	 * @param cronExpression
	 * @return
	 * @throws SchedulerException
	 */
	public boolean addJob(String jobName, String jobGroup, String cronExpression) throws SchedulerException {
		// TriggerKey : name + group
		TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
		JobKey jobKey = new JobKey(jobName, jobGroup);

		// TriggerKey valid if_exists
		if (checkExists(jobName, jobGroup)) {
			logger.info(">>>>>>>>> addJob fail, job already exist, jobGroup:{}, jobName:{}", jobGroup, jobName);
			return false;
		}

		// CronTrigger : TriggerKey + cronExpression //
		// withMisfireHandlingInstructionDoNothing 忽略掉调度终止过程中忽略的调度
		CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression)
				.withMisfireHandlingInstructionDoNothing();
		CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder)
				.build();

		// JobDetail : jobClass
		Class<? extends Job> jobClass_ = RemoteHttpJobBean.class; // Class.forName(jobInfo.getJobClass());

		JobDetail jobDetail = JobBuilder.newJob(jobClass_).withIdentity(jobKey).build();
		/*
		 * if (jobInfo.getJobData()!=null) { JobDataMap jobDataMap =
		 * jobDetail.getJobDataMap();
		 * jobDataMap.putAll(JacksonUtil.readValue(jobInfo.getJobData(),
		 * Map.class)); // JobExecutionContext
		 * context.getMergedJobDataMap().get("mailGuid"); }
		 */

		// schedule : jobDetail + cronTrigger
		Date date = scheduler.scheduleJob(jobDetail, cronTrigger);

		logger.info(">>>>>>>>>>> addJob success, jobDetail:{}, cronTrigger:{}, date:{}", jobDetail, cronTrigger, date);
		return true;
	}

	/**
	 * rescheduleJob
	 *
	 * @param jobGroup
	 * @param jobName
	 * @param cronExpression
	 * @return
	 * @throws SchedulerException
	 */
	public boolean rescheduleJob(String jobGroup, String jobName, String cronExpression) throws SchedulerException {

		// TriggerKey valid if_exists
		if (!checkExists(jobName, jobGroup)) {
			logger.info(">>>>>>>>>>> rescheduleJob fail, job not exists, JobGroup:{}, JobName:{}", jobGroup, jobName);
			return false;
		}

		// TriggerKey : name + group
		TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
		CronTrigger oldTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);

		if (oldTrigger != null) {
			// avoid repeat
			String oldCron = oldTrigger.getCronExpression();
			if (oldCron.equals(cronExpression)) {
				return true;
			}

			// CronTrigger : TriggerKey + cronExpression
			CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression)
					.withMisfireHandlingInstructionDoNothing();
			oldTrigger = oldTrigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(cronScheduleBuilder)
					.build();

			// rescheduleJob
			scheduler.rescheduleJob(triggerKey, oldTrigger);
		} else {
			// CronTrigger : TriggerKey + cronExpression
			CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression)
					.withMisfireHandlingInstructionDoNothing();
			CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey)
					.withSchedule(cronScheduleBuilder).build();

			// JobDetail-JobDataMap fresh
			JobKey jobKey = new JobKey(jobName, jobGroup);
			JobDetail jobDetail = scheduler.getJobDetail(jobKey);
			/*
			 * JobDataMap jobDataMap = jobDetail.getJobDataMap();
			 * jobDataMap.clear();
			 * jobDataMap.putAll(JacksonUtil.readValue(jobInfo.getJobData(),
			 * Map.class));
			 */

			// Trigger fresh
			HashSet<Trigger> triggerSet = new HashSet<Trigger>();
			triggerSet.add(cronTrigger);

			scheduler.scheduleJob(jobDetail, triggerSet, true);
		}

		logger.info(">>>>>>>>>>> resumeJob success, JobGroup:{}, JobName:{}", jobGroup, jobName);
		return true;
	}

	/**
	 * unscheduleJob
	 *
	 * @param jobName
	 * @param jobGroup
	 * @return
	 * @throws SchedulerException
	 */
	public boolean removeJob(String jobName, String jobGroup) throws SchedulerException {
		// TriggerKey : name + group
		TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
		boolean result = false;
		if (checkExists(jobName, jobGroup)) {
			result = scheduler.unscheduleJob(triggerKey);
			logger.info(">>>>>>>>>>> removeJob, triggerKey:{}, result [{}]", triggerKey, result);
		}
		return true;
	}

	/**
	 * pause
	 *
	 * @param jobName
	 * @param jobGroup
	 * @return
	 * @throws SchedulerException
	 */
	public boolean pauseJob(String jobName, String jobGroup) throws SchedulerException {
		// TriggerKey : name + group
		TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);

		boolean result = false;
		if (checkExists(jobName, jobGroup)) {
			scheduler.pauseTrigger(triggerKey);
			result = true;
			logger.info(">>>>>>>>>>> pauseJob success, triggerKey:{}", triggerKey);
		} else {
			logger.info(">>>>>>>>>>> pauseJob fail, triggerKey:{}", triggerKey);
		}
		return result;
	}

	/**
	 * resume
	 *
	 * @param jobName
	 * @param jobGroup
	 * @return
	 * @throws SchedulerException
	 */
	public boolean resumeJob(String jobName, String jobGroup) throws SchedulerException {
		// TriggerKey : name + group
		TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);

		boolean result = false;
		if (checkExists(jobName, jobGroup)) {
			scheduler.resumeTrigger(triggerKey);
			result = true;
			logger.info(">>>>>>>>>>> resumeJob success, triggerKey:{}", triggerKey);
		} else {
			logger.info(">>>>>>>>>>> resumeJob fail, triggerKey:{}", triggerKey);
		}
		return result;
	}

	/**
	 * run
	 *
	 * @param jobName
	 * @param jobGroup
	 * @return
	 * @throws SchedulerException
	 */
	public boolean triggerJob(String jobName, String jobGroup) throws SchedulerException {
		// TriggerKey : name + group
		JobKey jobKey = new JobKey(jobName, jobGroup);

		boolean result = false;
		if (checkExists(jobName, jobGroup)) {
			scheduler.triggerJob(jobKey);
			result = true;
			logger.info(">>>>>>>>>>> runJob success, jobKey:{}", jobKey);
		} else {
			logger.info(">>>>>>>>>>> runJob fail, jobKey:{}", jobKey);
		}
		return result;
	}

	/**
	 * finaAllJobList
	 *
	 * @return
	 *//*
		 * @Deprecated public static List<Map<String, Object>> finaAllJobList(){
		 * List<Map<String, Object>> jobList = new
		 * ArrayList<Map<String,Object>>();
		 * 
		 * try { if (scheduler.getJobGroupNames()==null ||
		 * scheduler.getJobGroupNames().size()==0) { return null; } String
		 * groupName = scheduler.getJobGroupNames().get(0); Set<JobKey> jobKeys
		 * = scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName)); if
		 * (jobKeys!=null && jobKeys.size()>0) { for (JobKey jobKey : jobKeys) {
		 * TriggerKey triggerKey = TriggerKey.triggerKey(jobKey.getName(),
		 * Scheduler.DEFAULT_GROUP); Trigger trigger =
		 * scheduler.getTrigger(triggerKey); JobDetail jobDetail =
		 * scheduler.getJobDetail(jobKey); TriggerState triggerState =
		 * scheduler.getTriggerState(triggerKey); Map<String, Object> jobMap =
		 * new HashMap<String, Object>(); jobMap.put("TriggerKey", triggerKey);
		 * jobMap.put("Trigger", trigger); jobMap.put("JobDetail", jobDetail);
		 * jobMap.put("TriggerState", triggerState); jobList.add(jobMap); } }
		 * 
		 * } catch (SchedulerException e) { logger.error(e.getMessage(), e);
		 * return null; } return jobList; }
		 */

}